package com.y.report.entry;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.y.report.cons.ApiConstant;
import com.y.report.entity.api.ApiRequestInfo;
import com.y.report.entity.api.ApiResponseInfo;
import com.y.report.entity.api.SqlExecInfo;
import com.y.report.entity.basic.YReportLog;
import com.y.report.entity.mapper.ApiReportInfo;
import com.y.report.exception.ApiExecption;
import com.y.report.hepler.SpringContextHolder;
import com.y.report.service.ReportService;
import com.y.report.service.api.ApiArgsService;
import com.y.report.service.api.ApiInterceptorService;
import com.y.report.service.api.ApiReportService;
import com.y.report.service.basic.YReportLogService;
import com.y.report.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

public class ApiEntry {

    private Logger log = LoggerFactory.getLogger(this.getClass());


    private ApiInterceptorService apiInterceptorService;

    private ReportService reportService;

    private ApiArgsService apiArgsService;

    private ApiReportService apiReportService;
    private YReportLogService reportLogService;


    private ApiRequestInfo apiRequestInfo;
    private ApiReportInfo apiReportInfo;
    private ApiResponseInfo apiResponseInfo;
    private Object report;
    private Map<String, Object> sqlArgs;

    public ApiEntry() {
        this.apiInterceptorService = SpringContextHolder.getBean(ApiInterceptorService.class);
        this.reportService = SpringContextHolder.getBean(ReportService.class);
        this.apiArgsService = SpringContextHolder.getBean(ApiArgsService.class);
        this.apiReportService = SpringContextHolder.getBean(ApiReportService.class);
        this.reportLogService = SpringContextHolder.getBean(YReportLogService.class);

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        final long currTime = System.currentTimeMillis();
        this.apiResponseInfo = new ApiResponseInfo(
                currTime,
                -1L,
                0L,
                Collections.emptyList(),
                "404",
                false,
                null,
                "OK",
                new LinkedHashMap<String, Object>(),
                IpUtil.getIpAddr(request),
                new LinkedHashMap<>(),
                new LinkedHashMap<>(),
                new LinkedHashMap<>(),
                null
        );
    }


    public boolean request() {
        log.debug("api entry start ...");
        //获取参数
        this.apiRequestInfo = apiReportService.init();

        //http拦截
        boolean isInterceptor = apiInterceptorService.interceptor(apiRequestInfo);

        if (isInterceptor) {
            log.debug("api entry interceptor ...");
            //查询api配置信息
            List<ApiReportInfo> apiReportList = reportService.getApiByUriOrName(
                    apiRequestInfo.getApiName(),
                    apiRequestInfo.getApiCode(),
                    apiRequestInfo.getApiAuth(),
                    apiRequestInfo.getApiUrl(),
                    apiRequestInfo.getApiMethod(),
                    null);

            if (apiReportList.size() > 1) {
                log.warn("接口存在重复，请排查元数据：{}", JSON.toJSONString(apiRequestInfo));
                apiReportList.sort(Comparator.comparingLong(x -> x.getYReportApi().getUTime().toInstant(ZoneOffset.of("+8")).toEpochMilli()));
            }
            if (CollUtil.isNotEmpty(apiReportList))
                this.apiReportInfo = apiReportList.get(0);
            else throw new ApiExecption("请求异常，接口不存在！");

            return apiInterceptorService.auth(apiRequestInfo, apiReportInfo) && apiArgsService.verify(apiRequestInfo, apiReportInfo);
        }
        return false;
    }

    public boolean exec() throws IOException {
        //解析参数
        apiArgsService.parseApiArgsInfo(apiRequestInfo);

        //解析el参数
        apiArgsService.parseTemplateApiArgsInfo(apiRequestInfo, apiReportInfo);

        //参数合并
        this.sqlArgs = apiArgsService.mergeArgs(
                apiArgsService.mergeArgs(
                        apiArgsService.mergeArgs(
                                apiRequestInfo.getUrlArgs(),
                                apiRequestInfo.getHeaderArgs(), ApiConstant.API_URL_REPEAT_ARGS_PREFIX
                        ),
                        apiRequestInfo.getBodyArgs(), ApiConstant.API_HEADER_REPEAT_ARGS_PREFIX
                ),
                apiRequestInfo.getCookieArgs(), ApiConstant.API_BODY_REPEAT_ARGS_PREFIX
        );

        //解析模板sql
        apiArgsService.parseSqlTemplate(sqlArgs, apiReportInfo);

        //api查询
        Pair<Object, List<SqlExecInfo>> report = apiReportService.report(apiReportInfo, sqlArgs);
        this.report = report.getKey();

        //响应信息-sql执行信息
        apiResponseInfo.setSqlExecInfos(report.getValue());

        return true;
    }

    public boolean response() throws IOException {
        try {
            //添加额外参数
            Map<String, Object> respArgs = this.sqlArgs;

            //响应信息-响应头
            Map<String, Object> respHeaders = apiArgsService.parseRespHeaders(respArgs, apiReportInfo);
            apiResponseInfo.setRespHeadersTemplate(respHeaders);

            //响应信息-cookie
            Map<String, Object> respCookies = apiArgsService.parseRespCookies(respArgs, apiReportInfo);
            apiResponseInfo.setRespCookiesTemplate(respCookies);

            //响应信息-响应体
            Map<String, Object> respBody = apiArgsService.parseRespBody(respArgs, apiReportInfo);
            apiResponseInfo.setRespBodyTemplate(respBody);

            //响应信息-api消耗时间
            apiResponseInfo.setEndTime(System.currentTimeMillis());
            apiResponseInfo.setElapsed(apiResponseInfo.getEndTime() - apiResponseInfo.getBeginTime());

            return true;
        } catch (Exception e) {
            buildErr(e);
        } finally {
            //api返回
            apiResponseInfo.setOutputData(report);
            apiResponseInfo.setStatus(apiReportInfo.getYReportApi().getRespStatus());
            Map<String, Object> responseBody = null;

            try {
                //扩展信息
                apiResponseInfo.setExtData(
                        Collections.singletonMap(ApiConstant.API_RESPONSE_SHOW_API_LOG_KEY,
                                buildApiLog(Arrays.asList(apiRequestInfo, apiResponseInfo, apiReportInfo))
                        )
                );
            } catch (Exception e) {
                buildErr(e);
            }
            try {
                //解析响应体
                responseBody = apiArgsService.parseRespBodyTemplate(sqlArgs, apiResponseInfo);
                apiResponseInfo.setRespBodyTemplate(responseBody);
            } catch (Exception e) {
                buildErr(e);
            }

            //添加api日志
            if (apiArgsService.parseApiShowLog(sqlArgs)) {
                responseBody.putAll(apiResponseInfo.getExtData());
            }

            //返回api
            apiReportService.finish(apiResponseInfo);
        }
        return false;
    }

    public void log() {
        apiResponseInfo.getSqlExecInfos().stream().map(SqlExecInfo::getBeginTs).map(Object::toString).collect(Collectors.joining(","));
        log.debug("记录api访问日志!");
        YReportLog yRrportLog = new YReportLog(
                null,
                apiResponseInfo.getReqIp(),
                apiResponseInfo.getBeginTime(),
                apiResponseInfo.getEndTime(),
                String.valueOf(apiResponseInfo.getElapsed()),
                apiResponseInfo.getSqlExecInfos().stream().map(SqlExecInfo::getBeginTs).map(Object::toString).collect(Collectors.joining(",")),
                apiResponseInfo.getSqlExecInfos().stream().map(SqlExecInfo::getEngTs).map(Object::toString).collect(Collectors.joining(",")),
                apiResponseInfo.getSqlExecInfos().stream().map(SqlExecInfo::getElapsedTs).map(Object::toString).collect(Collectors.joining(",")),
                JSON.toJSONString(buildApiLog(Arrays.asList(apiRequestInfo, apiReportInfo)), SerializerFeature.WriteMapNullValue),
                String.format("请求方法：%s，请求地址：%s，请求成功:%s", apiRequestInfo.getApiMethod(), apiRequestInfo.getApiUrl(), apiResponseInfo.getReturnFlag()),
                0,
                apiReportInfo.getYReportApi().getApiId(),
                LocalDateTime.now(),
                LocalDateTime.now()
        );
        reportLogService.appendMultiple(Collections.singletonList(yRrportLog));
    }

    private Map<String, Object> buildApiLog(List<Object> logObjs) {
        if (CollUtil.isNotEmpty(logObjs))
            return logObjs.stream()
                    .map(x -> Pair.of(x.getClass().getSimpleName(), JSON.parseObject(JSON.toJSONString(x, SerializerFeature.WriteMapNullValue), x.getClass())))
                    .collect(LinkedHashMap::new, (x, y) -> x.put(y.getKey(), y.getValue()), Map::putAll);
        return Collections.emptyMap();
    }

    private void buildErr(Throwable e) {
        log.warn(e.getMessage());
        apiResponseInfo.setErrorMessage(e.getMessage());
        apiResponseInfo.setMessage("FAIL");
        apiResponseInfo.setReturnFlag(false);
    }

    public ApiResponseInfo getApiResponseInfo() {
        return apiResponseInfo;
    }
}
